//=====Bounding_Box_H=================================
#pragma once 

#include "Point3D.h"
#include <vector>
#include <math.h>
#include <algorithm>
#include <vector>
#include "Ray.h"
#include <string>
namespace Engine
{

	using std::min;
	using std::max;
	using std::vector;
	using std::string;

	class BoundingBox
	{
	private:
		Point3D minPoint, maxPoint;

	public:
		BoundingBox()
		{
			minPoint = Point3D(INT_MAX);
			maxPoint = Point3D(INT_MIN);
		}

		BoundingBox(Point3D point)
		{
			minPoint = Point3D(point.x, point.y, point.z);
			maxPoint = Point3D(point.x, point.y, point.z);
		}

		BoundingBox(Point3D p_minPoint, Point3D p_maxPoint)
		{
			minPoint = Point3D(p_minPoint.x, p_minPoint.y, p_minPoint.z);
			maxPoint = Point3D(p_maxPoint.x, p_maxPoint.y, p_maxPoint.z);
		}

		Point3D GetMinPoint()
		{
			return minPoint;
		}

		Point3D GetMaxPoint()
		{
			return maxPoint;
		}

		void Expand(Point3D currentPoint)
		{
			minPoint.x = min(minPoint.x, currentPoint.x);
			minPoint.y = min(minPoint.y, currentPoint.y);
			minPoint.z = min(minPoint.z, currentPoint.z);

			maxPoint.x = max(maxPoint.x, currentPoint.x);
			maxPoint.y = max(maxPoint.y, currentPoint.y);
			maxPoint.z = max(maxPoint.z, currentPoint.z);
		}

	/*	void Expand(Point3D& currentPoint)
		{
			minPoint.x = min(minPoint.x, currentPoint.x);
			minPoint.y = min(minPoint.y, currentPoint.y);
			minPoint.z = min(minPoint.z, currentPoint.z);

			maxPoint.x = max(maxPoint.x, currentPoint.x);
			maxPoint.y = max(maxPoint.y, currentPoint.y);
			maxPoint.z = max(maxPoint.z, currentPoint.z);
		}*/

		bool Overlaps(BoundingBox b)
		{
			bool x = b.minPoint.x >= this->minPoint.x && b.minPoint.x <= minPoint.x;
			bool y = b.minPoint.y >= minPoint.y && b.minPoint.y <= minPoint.y;
			bool z = b.minPoint.z >= minPoint.z && b.minPoint.z <= minPoint.z;

			return x && y && z;
		}

		bool CheckPointInsideBox(Point3D pont)
		{
			bool x = pont.x >= minPoint.x && pont.x <= maxPoint.x;
			bool y = pont.y >= minPoint.y && pont.y <= maxPoint.y;
			bool z = pont.z >= minPoint.z && pont.z <= maxPoint.z;

			return x && y && z;
		}


		void Union(BoundingBox* b)
		{
			minPoint.x = min(minPoint.x, b->minPoint.x);
			minPoint.y = min(minPoint.y, b->minPoint.y);
			minPoint.z = min(minPoint.z, b->minPoint.z);

			maxPoint.x = max(maxPoint.x, b->maxPoint.x);
			maxPoint.y = max(maxPoint.y, b->maxPoint.y);
			maxPoint.z = max(maxPoint.z, b->maxPoint.z);
		}

		void Union(BoundingBox b)
		{
			minPoint.x = min(minPoint.x, b.minPoint.x);
			minPoint.y = min(minPoint.y, b.minPoint.y);
			minPoint.z = min(minPoint.z, b.minPoint.z);

			maxPoint.x = max(maxPoint.x, b.maxPoint.x);
			maxPoint.y = max(maxPoint.y, b.maxPoint.y);
			maxPoint.z = max(maxPoint.z, b.maxPoint.z);
		}

		// TODO check this with page 72
		int GetLongestAxis()
		{
			Point3D diagVector = maxPoint - minPoint;

			if (diagVector.x > diagVector.y && diagVector.x > diagVector.z)
			{
				return 0;
			}
			else if (diagVector.y > diagVector.z)
			{
				return 1;
			}
			else
			{
				return 2;
			}
		}

		double GetLongestLengthAxis()
		{
			Point3D diagVector = maxPoint - minPoint;

			if (diagVector.x > diagVector.y && diagVector.x > diagVector.z)
			{
				return diagVector.x;
			}
			else if (diagVector.y > diagVector.z)
			{
				return diagVector.y;
			}
			else
			{
				return diagVector.z;
			}
		}

		float GetMiddleOfSide(int side)
		{
			Point3D temp =( maxPoint - minPoint)/2;
			Point3D diagVector = temp + minPoint;
			switch (side)
			{

			case 0:
				return diagVector.x;
			case 1:
				return diagVector.y ;
			case 2:
				return diagVector.z;
			default:
				break;
			}
		}

		bool CheckIntersection(Ray& p_ray, double& tmin, double& tmax)
		{
			return CheckIntersection(p_ray.o, p_ray.d, tmin, tmax);
		}

		bool CheckIntersection(Point3D& radiancePoint, float& radius)
		{
			float dMin = 0;

			if (radiancePoint.x < minPoint.x)
				dMin += pow(radiancePoint.x - minPoint.x, 2);
			else if (radiancePoint.x > maxPoint.x)
				dMin += pow(radiancePoint.x - maxPoint.x, 2);

			if (radiancePoint.y < minPoint.y)
				dMin += pow(radiancePoint.y - minPoint.y, 2);
			else if (radiancePoint.y > maxPoint.y)
				dMin += pow(radiancePoint.y - maxPoint.y, 2);

			if (radiancePoint.z < minPoint.z)
				dMin += pow(radiancePoint.z - minPoint.z, 2);
			else if (radiancePoint.z > maxPoint.z)
				dMin += pow(radiancePoint.z - maxPoint.z, 2);
			//string output = to_string(dMin) + " this is dmin and the radius squared is " + to_string(radius*radius);

			//Print(output);
			return dMin < (radius * radius);

			//return CheckPointInsideBox(radiancePoint);

		}

		Point3D GetCentroid()
		{
			Point3D middle = maxPoint - minPoint;
			return minPoint + (middle / 2);
		}


		// TODO fix this
		bool CheckIntersection(Point3D& o, Point3D& d, double& tmin, double& tmax)
		{
			/*double tx1 = 0, tx2 = 0, ty1 = 0, ty2 = 0, tz1 = 0, tz2 = 0;

			tx1 = (minPoint.x - o.x) * d.x;
			tx2 = (maxPoint.x - o.x) * d.x;

			tmin = min(tx1, tx2);
			tmax = max(tx1, tx2);

			ty1 = (minPoint.y - o.y) * d.y;
			ty2 = (maxPoint.y - o.y) * d.y;

			tmin = min(tmin, min(ty1, ty2));
			tmax = max(tmax, max(ty1, ty2));

			tz1 = (minPoint.z - o.z) * d.z;
			tz2 = (maxPoint.z - o.z) * d.z;

			tmin = min(tmin, min(tz1, tz2));
			tmax = max(tmax, max(tz1, tz2));

			return (tmax >= tmin);*/

			Point3D dirfrac(0);

			dirfrac.x = 1.0f / d.x;
			dirfrac.y = 1.0f / d.y;
			dirfrac.z = 1.0f / d.z;
			// lb is the corner of AABB with minimal coordinates - left bottom, rt is maximal corner
			// r.org is origin of ray
			float t1 = (minPoint.x - o.x)*dirfrac.x;
			float t2 = (maxPoint.x - o.x)*dirfrac.x;
			float t3 = (minPoint.y - o.y)*dirfrac.y;
			float t4 = (maxPoint.y - o.y)*dirfrac.y;
			float t5 = (minPoint.z - o.z)*dirfrac.z;
			float t6 = (maxPoint.z - o.z)*dirfrac.z;

			tmin = max(max(min(t1, t2), min(t3, t4)), min(t5, t6));
			tmax = min(min(max(t1, t2), max(t3, t4)), max(t5, t6));

			// if tmax < 0, ray (line) is intersecting AABB, but whole AABB is behing us
			if (tmax < 0)
			{
				tmin = tmax;
				return false;
			}

			// if tmin > tmax, ray doesn't intersect AABB
			if (tmin > tmax)
			{
				float temp = tmax;
				tmin = tmax;
				tmax = temp;
				return false;
			}
			return true;

			/*float maxS = 0.0;
			float minT = FLT_MAX;

			float s, t;

			float recipX = 1 / d.x;
			float recipY = 1 / d.y;
			float recipZ = 1 / d.z;

			if (recipX >= 0.0f)
			{
				s = (minPoint.x - o.x) * recipX;
				t = (maxPoint.x - o.x) * recipX;
			}
			else
			{
				s = (maxPoint.x - o.x) * recipX;
				t = (minPoint.x - o.x) * recipX;
			}

			maxS = max(s, maxS);
			minT = min(t, minT);
				
			if (maxS > minT)
				return false;

			if (recipY >= 0.0f)
			{
				s = (minPoint.y - o.y) * recipY;
				t = (maxPoint.y - o.y) * recipY;
			}
			else
			{
				s = (maxPoint.y - o.y) * recipY;
				t = (minPoint.y - o.y) * recipY;
			}

			maxS = max(s, maxS);
			minT = min(t, minT);

			if (maxS > minT)
				return false;

			if (recipZ >= 0.0f)
			{
				s = (minPoint.z - o.z) * recipZ;
				t = (maxPoint.z - o.z) * recipZ;
			}
			else
			{
				s = (maxPoint.z - o.z) * recipZ;
				t = (minPoint.z - o.z) * recipZ;
			}

			maxS = max(s, maxS);
			minT = min(t, minT);

			if (maxS > minT)
				return false;

			tmin = maxS;
			tmax = minT;

			return true;*/
		}

		string ToString()
		{
			string output = minPoint.toString() + "--" + maxPoint.toString();
			return output;
		}

	};
}

//=====Bounding_Box_H=================================